﻿namespace MefContrib.Models.Provider.Definitions.Fluent
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq;
    using System.Linq.Expressions;
    using MefContrib.Models.Provider.Validation;

    public abstract class Decorator
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="Decorator"/> class, using
        /// the specified <see cref="Provider"/> instance.
        /// </summary>
        /// <param name="provider">The <see cref="FluentDefinitionProvider"/> object to decorate.</param>
        protected Decorator(FluentDefinitionProvider provider)
        {
            Provider = provider;
            CurrentPart = Provider.Descriptions.LastOrDefault();
        }

        /// <summary>
        /// Gets the currently active <see cref="PartDescription"/> object.
        /// </summary>
        /// <value>A <see cref="PartDescription"/> object.</value>
        public PartDescription CurrentPart { get; protected set; }

        /// <summary>
        /// Gets the <see cref="Provider"/> object which is being decorated.
        /// </summary>
        /// <value>A <see cref="Provider"/> object.</value>
        public FluentDefinitionProvider Provider { get; protected set; }

        /// <summary>
        /// Registers an export with no return value.
        /// </summary>
        /// <typeparam name="T">The <see cref="Type"/> that the export is registered for.</typeparam>
        /// <returns>A <see cref="ExportDecorator"/> object.</returns>
        /// <remarks>This overload handles methods with no return (void) value.</remarks>
        [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "The lambda expression is required to identify the export.")]
        public virtual ExportDecorator Export<T>()
        {
            var description = new ExportDescription
                                  {
                                      MemberName = null,
                                      Metadata = new Dictionary<string, object>()
                                  };

            var decorator = new ExportDecorator(Provider) { CurrentPart = GetPart(typeof(T)) };
            decorator.CurrentPart.Exports.Add(description);

            return decorator;
        }

        /// <summary>
        /// Registers an export with no return value.
        /// </summary>
        /// <typeparam name="T">The <see cref="Type"/> that the export is registered for.</typeparam>
        /// <param name="exportExpression">A lambda expression used to identify the export.</param>
        /// <returns>A <see cref="ExportDecorator"/> object.</returns>
        /// <remarks>This overload handles methods with no return (void) value.</remarks>
        /// <exception cref="ArgumentNullException">The specified value for the <paramref name="exportExpression"/> parameter was <see langword="null"/>.</exception>
        [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "The lambda expression is required to identify the export.")]
        public virtual ExportDecorator Export<T>(Expression<Action<T>> exportExpression)
        {
            exportExpression
                .Require("exportExpression")
                .NotNull();

            var nameOfMember = GetMemberName(exportExpression);
            var description = new ExportDescription
            {
                MemberName = nameOfMember,
                Metadata = new Dictionary<string, object>()
            };

            var decorator = new ExportDecorator(Provider) { CurrentPart = GetPart(typeof(T)) };
            decorator.CurrentPart.Exports.Add(description);

            return decorator;
        }

        public virtual ExportDecorator Export<T, TD>(Expression<Func<T, TD>> exportExpression)
        {
            return null;
        }
        /// <summary>
        /// Registers an export with a return value.
        /// </summary>
        /// <typeparam name="T">The <see cref="Type"/> that the export is being registered for.</typeparam>
        /// <param name="exportExpression">A lambda expression used to identify the export.</param>
        /// <returns>An <see cref="ExportDecorator"/> object.</returns>
        /// <remarks>This overload handled properties, fields, methods with a return value and classes.</remarks>
        /// <exception cref="ArgumentNullException">The specified value for the <paramref name="exportExpression"/> parameter was <see langword="null"/>.</exception>
        [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "The lambda expression is required to identify the export.")]
        public virtual ExportDecorator Export<T>(Expression<Func<T, object>> exportExpression)
        {
            exportExpression
                .Require("exportExpression")
                .NotNull();

            var nameOfMember = GetMemberName(exportExpression);
            var description = new ExportDescription
            {
                MemberName = nameOfMember,
                Metadata = new Dictionary<string, object>()
            };

            var decorator = new ExportDecorator(Provider) { CurrentPart = GetPart(typeof(T)) };
            decorator.CurrentPart.Exports.Add(description);

            return decorator;
        }

        /// <summary>
        /// Registers an import.
        /// </summary>
        /// <typeparam name="T">The <see cref="Type"/> that the import is being registered for.</typeparam>
        /// <param name="importExpression">A lambda expression used to identify the import.</param>
        /// <returns>An <see cref="ImportDecorator"/></returns> object.
        /// <exception cref="ArgumentNullException">The specified value for the <paramref name="importExpression"/> parameter was <see langword="null"/>.</exception>
        [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "The lambda expression is required to identify the import.")]
        public virtual ImportDecorator Import<T>(Expression<Func<T, object>> importExpression)
        {
            importExpression
                .Require("importExpression")
                .NotNull();

            var memberName = GetMemberName(importExpression);

            var description = new ImportDescription
                                  {
                                      MemberName = memberName,
                                      RequiredMetadata = new Collection<string>()
                                  };

            var decorator = new ImportDecorator(Provider) { CurrentPart = GetPart(typeof(T)) };
            decorator.CurrentPart.Imports.Add(description);

            return decorator;
        }

        /// <summary>
        /// Retrieves the name of the targeted member in an expression tree.
        /// </summary>
        /// <param name="expression">The <see cref="Expression"/> to get the name from.</param>
        /// <returns>The name of the member if it could be located; otherwise <see langword="null"/>.</returns>
        /// <exception cref="ArgumentNullException">The provided value for the <paramref name="expression"/> parameter was <see langword="null"/>.</exception>
        protected string GetMemberName(Expression expression)
        {
            expression
                .Require("expression")
                .NotNull();

            string memberName = null;

            switch (expression.NodeType)
            {
                case ExpressionType.Lambda:
                    return GetMemberName(((LambdaExpression)expression).Body);
                case ExpressionType.Call:
                    memberName = ((MethodCallExpression)expression).Method.Name;
                    break;
                case ExpressionType.Convert:
                    return GetMemberName(((UnaryExpression)expression).Operand);
                case ExpressionType.MemberAccess:
                    memberName = ((MemberExpression)expression).Member.Name;
                    break;
                case ExpressionType.Parameter:
                    break;
            }

            return memberName;
        }

        /// <summary>
        /// Retrieves the part for the specified <see cref="Type"/>.
        /// </summary>
        /// <param name="typeOfPart">The <see cref="Type"/> to get the part for.</param>
        /// <returns>A <see cref="PartDescription"/> object.</returns>
        /// <exception cref="ArgumentNullException">The specified value for the <paramref name="typeOfPart"/> parameter was null.</exception>
        /// <remarks>If the part could not be found for the type then it is created and stored in the cache for future requests.</remarks>
        protected PartDescription GetPart(Type typeOfPart)
        {
            typeOfPart
                .Require("typeOfPart")
                .NotNull();

            var exists =
                from p in Provider.Descriptions
                where string.Compare(p.TypeName, typeOfPart.AssemblyQualifiedName, StringComparison.InvariantCultureIgnoreCase) == 0
                select p;

            var locatedPart = (exists.Count() > 0) ?
                exists.First() : new PartDescription { TypeName = typeOfPart.AssemblyQualifiedName };

            if (exists.Count() == 0)
            {
                locatedPart.Exports = new Collection<ExportDescription>();
                locatedPart.Imports = new Collection<ImportDescription>();
                Provider.Descriptions.Add(locatedPart);
            }

            return locatedPart;
        }
    }
}
